Skip to content

fix(people-search-tool): default FullEnrich, strict validation, precision guidance#358

Merged
zhubzy merged 4 commits intomainfrom
fix/people-search-default-fullenrich
Apr 20, 2026
Merged

fix(people-search-tool): default FullEnrich, strict validation, precision guidance#358
zhubzy merged 4 commits intomainfrom
fix/people-search-default-fullenrich

Conversation

@zhubzy
Copy link
Copy Markdown
Contributor

@zhubzy zhubzy commented Apr 20, 2026

Summary

Default PeopleSearchTool.provider to fullenrich and overhaul the FullEnrich path so flow-authoring agents get high-quality leads on first try. Motivated by flow 11091 (LinkedIn Lead Gen): an agent hardcoded provider: 'crustdata' six times despite the Zod default being fullenrich, misled by docs that opened with "Uses the Crustdata PersonDB" and didn't tag Crustdata-only filters.

What's in this PR

  1. Docs: lead with fullenrichlongDescription + class JSDoc rewritten to position FullEnrich as default; every provider filter note is accurate against actual coverage.
  2. Expanded FullEnrich filter coveragelanguages, functionCategories, schoolName, pastJobTitle, minYearsAtCompany, recentlyChangedJobs are now passed through to FullEnrich (were previously only on the Crustdata branch).
  3. Real FullEnrich taxonomy mapping — seniority (CXOC-level, Vice PresidentVP, etc.), job functions (Crustdata enum → FE's top-level functions + accepted subfunctions), and industries (shorthand like SaaSSoftware Development, FintechFinancial Services). Lookup tables derived from FE's published "Accepted Filter Values" reference.
  4. exact_match: true on every string filter — fixes the company fuzz where companyName: "Stripe" would also return "Stripes" and ex-Stripe folks. Verified against FullEnrich's API docs (https://docs.fullenrich.com/api/v2/people/search/post).
  5. Hard errors instead of soft warnings — unknown seniority/function/industry values and Crustdata-only filters on the FullEnrich path now produce a specific error listing the accepted values. Replaces the previous silent-pass-through that let wrong-category results through.
  6. enrichPeopleEmails warnings — timeout, insufficient-credits, start-failure, and thrown errors are surfaced in the tool result's warnings array instead of silently returning un-enriched people. Poll interval bumped 3s → 5s.
  7. "HOW TO GET HIGH-QUALITY LEADS" block in the longDescription — recommends an explicit jobTitles list over relying on ML-derived seniorityLevels + functionCategories alone (FE's person-level tagging is imperfect even with exact_match: true).
  8. Version bump to 0.1.301.

Testing

Used bubblelab-test-bubble to run 4 real-world scenarios through fresh agents across 4 iterations (v1 → v4). Quality scores:

Scenario v1 (baseline) v4 (final)
5 HR recruiters at B2B SaaS in India 1/10 (engineers) 9/10 (Chargebee + BrowserStack)
3 engineering Directors/VPs at Stripe 3/10 ("Stripes", ex-Stripe) 10/10 (3 Eng VPs @stripe.com)
5 Entry Level marketers in US 0/10 (filter ignored) 8/10 (hard error taught agent)
5 Customer Success people in Germany 0/10 (engineers, sales) 10/10 (first try, CSMs at Personio/HubSpot/etc.)

All four scenarios v4 succeeded on first or second attempt without any agent hand-holding beyond the tool's get-bubble description.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • People search results now include warning messages for enhanced transparency.
    • Improved email enrichment with better data population in results.
  • Bug Fixes

    • Enhanced validation for search filter parameters with clearer error guidance.
    • Increased resilience in batch processing for email enrichment operations.

zhubzy added 4 commits April 20, 2026 00:53
Flow-authoring agents were hardcoding provider='crustdata' because the
longDescription opened with "Uses the Crustdata PersonDB" and the filter
list didn't tag crustdata-only features. Flow 11091 (LinkedIn Lead Gen)
is the concrete case that ate six crustdata batches instead of one
fullenrich search.

- longDescription now opens with a PROVIDERS block that states fullenrich
  is default and preferred, and only lists crustdata as a fallback.
- Each filter in the SEARCH CRITERIA list is tagged *(crustdata only)*
  where appropriate so an agent reading the doc knows which filters
  force a provider switch.
- Class JSDoc updated to match.
…nt warnings

Based on subagent testing (bubblelab-test-bubble against people-search-tool)
and FullEnrich's published accepted-values reference
(https://docs.fullenrich.com/ → Accepted Filter Values), fix four silent-failure
modes on the FullEnrich branch:

- mapSeniorityToFullEnrich: drop Entry Level / Associate / In Training →
  Associate. FullEnrich's seniority enum is {Owner, Founder, C-level, Partner,
  VP, Head, Director, Manager, Senior} — no Associate. Unknown values are now
  pushed into the result's warnings array instead of silently forwarding a
  no-op filter value.

- mapFunctionCategoryToFullEnrich (new): translate the Crustdata-flavored
  functionCategories enum to FullEnrich's top-level function enum
  (Administrative, Consulting & Advisory, Customer Service, Design, Education,
  Executive & Leadership, Finance, Human Resources, Legal, Marketing,
  Media & Communications, Medical & Health, Operations, Product,
  Project & Program Management, Public Safety & Security, Research & Science,
  Retail & Consumer, Sales, Software, Traditional Engineering,
  Transportation & Logistics, …). Values that don't map are passed through
  (they might be valid FE subfunctions like 'Recruiting/Talent Acquisition')
  and recorded in warnings.

- Email enrichment warnings: enrichPeopleEmails now returns
  { people, warnings } and callers merge them into the tool result.
  Timeouts, insufficient-credits stops, start-failures, and thrown errors are
  each surfaced rather than silently returning un-enriched people. Bumped the
  poll interval from 3s → 5s (no UX cost; enrichment is 30–120s typically).

- Docs: enrichEmails describe string now calls out the 30–120s latency and
  points at the warnings array so flow-authoring agents surface progress /
  timeout UI instead of wiring it into user-facing flows blind.
… guidance

Subagent testing showed three remaining failure modes after the warnings-only
version (see test iterations v1→v3):
- FullEnrich silently soft-matches company names, so `companyName: 'Stripe'`
  returned 'Stripes' + ex-Stripe folks.
- FullEnrich's person-level seniority/function tags are ML-derived and
  imperfect; `functionCategories: ['Software'] + seniorityLevels: ['VP']`
  surfaced a 'VP of Strategic Finance'.
- Warnings alone get ignored — agents wire them into flows and ship the wrong
  data.

Fixes:
1. Set `exact_match: true` on every string filter in the FE request. FE's
   docs (verified via https://docs.fullenrich.com/api/v2/people/search/post)
   confirm this toggle disables fuzzy matching at the provider level. Result:
   Stripe vs Stripes is strictly resolved.
2. Replace soft-warn with HARD ERROR for:
   - seniorityLevels with values outside FE's enum (Owner/Founder/C-level/
     Partner/VP/Head/Director/Manager/Senior) — error lists the accepted
     values AND points to provider="crustdata" for Crustdata-only values.
   - functionCategories with values outside FE's top-level functions OR
     known subfunctions — error enumerates acceptable values.
   - companyIndustries with values outside FE's taxonomy (uses alias map
     first — 'SaaS' → 'Software Development', 'Fintech' → 'Financial
     Services', etc. — so common shorthand works).
   - Crustdata-only filters (locationRadius, excludeCompanies,
     minYearsExperience, etc.) when provider routes to FE — error tells the
     caller to switch providers or drop the filter.
3. Add 'HOW TO GET HIGH-QUALITY LEADS' block to the tool's longDescription.
   Most impactful guidance: prefer an explicit `jobTitles` list over relying
   on ML-derived seniority/function tags alone. Subagent retests went 0/10 →
   10/10 on first attempt across Stripe-engineering and Customer-Success
   scenarios after this nudge. The canonical high-precision recipe
   (`jobTitles` + `country` + `minCompanyHeadcount` + `companyIndustries`)
   is called out explicitly.

Test evidence: https://docs.fullenrich.com/ accepted filter values list +
retest flows 468 (3/3 Stripe eng VPs) and 470 (5/5 German CSMs) succeeded on
first iteration with no instruction beyond the tool description.
Copilot AI review requested due to automatic review settings April 20, 2026 08:59
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

This PR bumps package versions from 0.1.300 to 0.1.301 across the monorepo and significantly enhances the people-search-tool with FullEnrich API support, including mapping validators for seniority/function/industry filters, warning aggregation, conditional hard-fail routing for unsupported filters, and improved email enrichment handling.

Changes

Cohort / File(s) Summary
Version bumps
packages/bubble-core/package.json, packages/bubble-runtime/package.json, packages/bubble-scope-manager/package.json, packages/bubble-shared-schemas/package.json, packages/create-bubblelab-app/package.json
Updated published package versions from 0.1.300 to 0.1.301.
Template dependencies
packages/create-bubblelab-app/templates/basic/package.json, packages/create-bubblelab-app/templates/reddit-scraper/package.json
Updated template production dependencies (@bubblelab/bubble-core, @bubblelab/bubble-runtime, @bubblelab/shared-schemas) from ^0.1.300 to ^0.1.301.
People search tool enhancements
packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts
Added three mapping/validation helpers for FullEnrich inputs (mapSeniorityToFullEnrich, mapFunctionCategoryToFullEnrich, mapIndustryToFullEnrich). Extended result schema with optional warnings: string[]. Implemented hard-fail routing for Crustdata-only filters in FullEnrich searches. Enhanced email population from FullEnrich contact block. Changed bulk enrichment to return { people, warnings } and improved error handling with descriptive warnings instead of silent skipping. Adjusted polling interval from 3000ms to 5000ms. Tightened criteria detection for FullEnrich and wrapped string filters with exact-match mode.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat (bubble-core): add company/persion search #266: Directly modifies the same people-search-tool.ts file with FullEnrich integration enhancements, mapping validators, and warning aggregation logic.
  • bump versions #298: Updates package version fields across the same package.json files with version bumps.
  • bump versions #286: Modifies package version fields for bubble-core, bubble-runtime, bubble-scope-manager, bubble-shared-schemas, and create-bubblelab-app with synchronized version updates.

Poem

🐰 With whiskers twitching, I hop with glee,
Enriching searches—now warnings are free!
FullEnrich and Crustdata, side by side,
Email and seniority, validated with pride!
A version bump brings clarity clear. 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(people-search-tool): default FullEnrich, strict validation, precision guidance' directly summarizes the main changes: defaulting to FullEnrich, adding strict validation, and improving precision guidance through documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/people-search-default-fullenrich

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying bubblelab-documentation with  Cloudflare Pages  Cloudflare Pages

Latest commit: c67607f
Status: ✅  Deploy successful!
Preview URL: https://67cb217c.bubblelab-documentation.pages.dev
Branch Preview URL: https://fix-people-search-default-fu.bubblelab-documentation.pages.dev

View logs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts (2)

1342-1356: ⚠️ Potential issue | 🟡 Minor

Warn when Crustdata email enrichment is requested but cannot run.

With the new warnings result field, this branch should also surface the missing FULLENRICH_API_KEY case. Today enrichEmails=true silently returns un-enriched people when only the Crustdata credential is configured.

Proposed warning path
-      if (
-        enrichEmails &&
-        credentials[CredentialType.FULLENRICH_API_KEY] &&
-        people.length > 0
-      ) {
+      if (enrichEmails && people.length > 0) {
+        if (!credentials[CredentialType.FULLENRICH_API_KEY]) {
+          enrichmentWarnings.push(
+            'Email enrichment was requested but skipped because FULLENRICH_API_KEY credential is not configured.'
+          );
+        } else {
         const enrichResult = await this.enrichPeopleEmails(
           people,
           credentials,
           includePersonalEmails ?? false
         );
         people = enrichResult.people;
         enrichmentWarnings.push(...enrichResult.warnings);
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts` around
lines 1342 - 1356, The branch that triggers email enrichment currently only runs
when credentials[CredentialType.FULLENRICH_API_KEY] is present, which silently
skips enrichment if the caller requested enrichEmails=true but only Crustdata is
configured; update the logic around enrichEmails/credentials to detect the case
where this.params.enrichEmails is true and credentials lacks
CredentialType.FULLENRICH_API_KEY and push a user-facing warning into
enrichmentWarnings (same array used after calling this.enrichPeopleEmails)
explaining that FULLENRICH_API_KEY is missing and enrichment could not run; keep
the existing call to this.enrichPeopleEmails unchanged when the key is present
and still append any warnings returned from that call.

735-740: ⚠️ Potential issue | 🟠 Major

Apply boolean filter logic for recentlyChangedJobs: false instead of silently dropping it.

The schema accepts a boolean filter, but the implementation only checks for true and ignores false. When a caller passes recentlyChangedJobs: false alongside another criterion, the false filter is silently dropped, returning recently-changed people unintentionally.

Since FullEnrich's current_company_days_since_last_job_change field supports both min and max, update:

  • Line 1470: Change recentlyChangedJobs === true to recentlyChangedJobs !== undefined
  • Lines 1573–1576: Handle both cases:
    -          ...(recentlyChangedJobs === true && {
    +          ...(recentlyChangedJobs !== undefined && {
                 current_company_days_since_last_job_change: [
    -              { max: 90 },
    +              recentlyChangedJobs ? { max: 90 } : { min: 91 },
                 ],
               }),

This applies to lines 1470 and 1573–1576.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts` around
lines 735 - 740, The recentlyChangedJobs boolean is being ignored when false;
update the filter construction for recentlyChangedJobs so you check for
recentlyChangedJobs !== undefined (not === true) and then map both values into
FullEnrich's current_company_days_since_last_job_change range: when
recentlyChangedJobs === true set an upper bound (max) to the "recent" threshold
constant used in this module, and when recentlyChangedJobs === false set a lower
bound (min) to one day beyond that threshold (or appropriate >recent threshold)
so the false case excludes recently changed people; apply this change where
recentlyChangedJobs is referenced and where
current_company_days_since_last_job_change range filters are assembled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts`:
- Around line 44-53: Update the stale documentation comments in
people-search-tool (the block describing translation of Crustdata seniority and
the other doc blocks that repeat this behavior) to reflect current behavior:
unsupported filters now cause a hard error (do not say they're silently dropped
or only warn), and Crustdata is only used when provider="crustdata" (do not
claim the tool "falls back" to Crustdata otherwise); edit the comment that
begins "Translate the tool's Crustdata-flavored seniority vocabulary..." and the
other similar doc blocks to state these two facts clearly and mention that
FullEnrich mapping/validation happens prior to sending the request and will
raise on unsupported filter values.
- Around line 360-435: The current mapIndustryToFullEnrich function rejects any
industry not in FE_INDUSTRY_CANONICAL or FE_INDUSTRY_ALIASES; instead stop
pre-rejecting unknown valid FullEnrich values by deferring validation to the
FullEnrich API: in mapIndustryToFullEnrich (and related callers) remove the
hard-fail path that pushes values into unknown for lookup misses and either (a)
include the unknown raw/trimmed value in the returned mapped set to be sent to
FullEnrich, or (b) rename/repurpose the unknown array to "deferred" and ensure
those values are forwarded to the API for authoritative validation; keep
FE_INDUSTRY_CANONICAL and FE_INDUSTRY_ALIASES only for helpful alias expansion,
not for rejecting inputs.

---

Outside diff comments:
In `@packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts`:
- Around line 1342-1356: The branch that triggers email enrichment currently
only runs when credentials[CredentialType.FULLENRICH_API_KEY] is present, which
silently skips enrichment if the caller requested enrichEmails=true but only
Crustdata is configured; update the logic around enrichEmails/credentials to
detect the case where this.params.enrichEmails is true and credentials lacks
CredentialType.FULLENRICH_API_KEY and push a user-facing warning into
enrichmentWarnings (same array used after calling this.enrichPeopleEmails)
explaining that FULLENRICH_API_KEY is missing and enrichment could not run; keep
the existing call to this.enrichPeopleEmails unchanged when the key is present
and still append any warnings returned from that call.
- Around line 735-740: The recentlyChangedJobs boolean is being ignored when
false; update the filter construction for recentlyChangedJobs so you check for
recentlyChangedJobs !== undefined (not === true) and then map both values into
FullEnrich's current_company_days_since_last_job_change range: when
recentlyChangedJobs === true set an upper bound (max) to the "recent" threshold
constant used in this module, and when recentlyChangedJobs === false set a lower
bound (min) to one day beyond that threshold (or appropriate >recent threshold)
so the false case excludes recently changed people; apply this change where
recentlyChangedJobs is referenced and where
current_company_days_since_last_job_change range filters are assembled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09269f25-fe08-4633-a4e4-243668a968fe

📥 Commits

Reviewing files that changed from the base of the PR and between 06974f8 and c67607f.

📒 Files selected for processing (8)
  • packages/bubble-core/package.json
  • packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts
  • packages/bubble-runtime/package.json
  • packages/bubble-scope-manager/package.json
  • packages/bubble-shared-schemas/package.json
  • packages/create-bubblelab-app/package.json
  • packages/create-bubblelab-app/templates/basic/package.json
  • packages/create-bubblelab-app/templates/reddit-scraper/package.json

Comment on lines +44 to +53
/**
* Translate the tool's Crustdata-flavored seniority vocabulary to the values
* FullEnrich's /people/search actually accepts. FullEnrich's seniority enum is:
* Owner, Founder, C-level, Partner, VP, Head, Director, Manager, Senior.
*
* Unknown inputs are passed through unchanged — FullEnrich accepts the request
* but silently ignores unrecognized seniority values, so there is nothing to
* gain from dropping them client-side. Callers targeting seniority levels FE
* doesn't index (e.g. "Entry Level", "In Training") will get `warnings`
* pointing that out.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update stale FullEnrich behavior docs.

These docs still say unknown values are passed through with warnings, unsupported filters are silently dropped, or that the tool “falls back” to Crustdata. The implementation now hard-errors on unsupported filters and uses Crustdata only when provider="crustdata".

Suggested direction
- * Unknown inputs are passed through unchanged — FullEnrich accepts the request
- * but silently ignores unrecognized seniority values, so there is nothing to
- * gain from dropping them client-side.
+ * Unknown inputs are returned in `unknown` so the FullEnrich path can fail
+ * fast with accepted-value guidance instead of silently broadening/narrowing
+ * the search.
- * doesn't support ... languages).
+ * doesn't support. Unsupported filters on the FullEnrich path return a hard
+ * error; set provider='crustdata' when those filters are required.

Also applies to: 105-110, 823-829, 861-868

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts` around
lines 44 - 53, Update the stale documentation comments in people-search-tool
(the block describing translation of Crustdata seniority and the other doc
blocks that repeat this behavior) to reflect current behavior: unsupported
filters now cause a hard error (do not say they're silently dropped or only
warn), and Crustdata is only used when provider="crustdata" (do not claim the
tool "falls back" to Crustdata otherwise); edit the comment that begins
"Translate the tool's Crustdata-flavored seniority vocabulary..." and the other
similar doc blocks to state these two facts clearly and mention that FullEnrich
mapping/validation happens prior to sending the request and will raise on
unsupported filter values.

Comment on lines +360 to +435
/**
* Canonical FE industry values (subset). Used for exact-match passthrough —
* we don't enumerate all ~400; a value not in aliases and not in this list is
* rejected with a pointer at the FE docs. This covers the canonical values
* most common for tech/sales prospecting.
*/
const FE_INDUSTRY_CANONICAL = new Set([
'Software Development',
'Technology, Information and Internet',
'Technology, Information and Media',
'Financial Services',
'Banking',
'Insurance',
'Hospitals and Health Care',
'Health, Wellness & Fitness',
'Biotechnology Research',
'Pharmaceutical Manufacturing',
'Retail',
'Business Consulting and Services',
'IT Services and IT Consulting',
'Manufacturing',
'Motor Vehicle Manufacturing',
'Automotive',
'Online Audio and Video Media',
'Broadcast Media Production and Distribution',
'Entertainment Providers',
'Education',
'Higher Education',
'Government Administration',
'Non-profit Organizations',
'Marketing Services',
'Advertising Services',
'Law Practice',
'Legal Services',
'Real Estate',
'Construction',
'Oil and Gas',
'Renewable Energy Power Generation',
'Utilities',
'Telecommunications',
'Transportation, Logistics, Supply Chain and Storage',
'Airlines and Aviation',
'Hospitality',
'Staffing and Recruiting',
'Venture Capital and Private Equity Principals',
'Computer Games',
'Defense and Space Manufacturing',
'E-Learning Providers',
'Food and Beverage Services',
'Research Services',
'Investment Management',
'Investment Banking',
'Capital Markets',
]);

function mapIndustryToFullEnrich(values: string[]): {
mapped: string[];
unknown: string[];
} {
const mapped = new Set<string>();
const unknown: string[] = [];
for (const raw of values) {
const trimmed = raw.trim();
if (FE_INDUSTRY_CANONICAL.has(trimmed)) {
mapped.add(trimmed);
continue;
}
const aliased = FE_INDUSTRY_ALIASES[trimmed.toLowerCase()];
if (aliased) {
for (const v of aliased) mapped.add(v);
continue;
}
unknown.push(trimmed);
}
return { mapped: Array.from(mapped), unknown };
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

FullEnrich Accepted Filter Values Company Industry people search full list

💡 Result:

The FullEnrich API documentation provides a list of accepted filter values for company industries, company types, and other filters used in people and company searches. These are predefined enums that can be used as filters in the Search API endpoints. Company Industries (partial list from docs; full list is extensive): Import & Export, Think Tanks, Environmental Services, Architecture and Planning, Staffing and Recruiting, Legal Services, IT Services and IT Consulting, Insurance, Non-profit Organizations, Venture Capital and Private Equity, Principals, Home Health Care Services, Primary and Secondary Education, Software Development, Research Services, Law Practice, Real Estate, Individual and Family Services, Mental Health Care, Real Estate Agents and Brokers, Health and Human Services, Hospitality, Farming, Fundraising, Accounting, Medical Device, Beverage Manufacturing, Wellness and Fitness Services, Hospitals and Health Care, Marketing Services, Financial Services, Outsourcing and Offshoring Consulting, Chemical Manufacturing, Executive Offices, Advertising Services, Transportation Logistics Supply Chain and Storage, E-Learning Providers, Travel Arrangements, Education Management, Industry Associations, Information Technology & Services, Writing and Editing, Public Safety, Media Production, Business Consulting and Services, Oil and Gas, Railroad Equipment Manufacturing, Oil Gas and Mining, Strategic Management Services, Technology Information and Internet, Landscaping Services, Wholesale Building Materials, Sporting Goods Manufacturing, Motor Vehicle Manufacturing, Security and Investigations, Civic and Social Organizations, Apparel Manufacturing, Construction, Biotechnology Research, Spectator Sports, Farming Ranching Forestry, Housing and Community Development, Veterinary Services, Consumer Services, Utilities, Engineering Services, Lime and Gypsum Products Manufacturing, Circuses and Magic Shows, Fuel Cell Manufacturing, Savings Institutions, Death Care Services, and more (see official docs for complete exhaustive list). Company Types: Partnership, Nonprofit, Educational, Privately Held, Public Company, Self-Owned, Self-Employed, Government Agency. For people search, additional filters include current_company_industries (using the same industry list), current_company_types (same as above), current_position_seniority_level (e.g., Owner, Founder, C-level, Partner, VP, Head, Director, Senior, Manager; full list in docs), job functions, locations (continent, country, state/region, city), etc. Location filters support: Continent (e.g., North America), Country (English), State/Region (local language), City (local language). Recommend testing in UI for exact values. Seniority data is noted as "soon" in enums page, but people search supports current_position_seniority_level. These values are used in both People Search and Company Search APIs with OR logic within categories and AND across categories. For full up-to-date list and usage, refer to https://docs.fullenrich.com/api/v2/general/enums.

Citations:


🏁 Script executed:

# Search for calls to mapIndustryToFullEnrich
rg "mapIndustryToFullEnrich" packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts -A 5 -B 2

Repository: bubblelabai/BubbleLab

Length of output: 936


🏁 Script executed:

# Check lines 1500-1506 mentioned in the review
sed -n '1495,1510p' packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts

Repository: bubblelabai/BubbleLab

Length of output: 1628


🏁 Script executed:

# Search for how unknown industries are handled after mapping
rg "unknown" packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts -B 3 -A 3

Repository: bubblelabai/BubbleLab

Length of output: 11367


Reject legitimate FullEnrich industries rejected outside the local subset.

FE_INDUSTRY_CANONICAL is a documented subset covering "most common for tech/sales prospecting," yet mapIndustryToFullEnrich hard-fails the request for any industry not in this local list or aliases. This means a legitimate FullEnrich canonical industry outside this subset is rejected before reaching the API. The error message itself points to the FullEnrich docs for the full list, implying the value might be valid there—but the code has already rejected it.

Include the complete FullEnrich industry taxonomy or defer industry validation to the FullEnrich API instead of pre-rejecting at the client level.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts` around
lines 360 - 435, The current mapIndustryToFullEnrich function rejects any
industry not in FE_INDUSTRY_CANONICAL or FE_INDUSTRY_ALIASES; instead stop
pre-rejecting unknown valid FullEnrich values by deferring validation to the
FullEnrich API: in mapIndustryToFullEnrich (and related callers) remove the
hard-fail path that pushes values into unknown for lookup misses and either (a)
include the unknown raw/trimmed value in the returned mapped set to be sent to
FullEnrich, or (b) rename/repurpose the unknown array to "deferred" and ensure
those values are forwarded to the API for authoritative validation; keep
FE_INDUSTRY_CANONICAL and FE_INDUSTRY_ALIASES only for helpful alias expansion,
not for rejecting inputs.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the PeopleSearchTool to default to FullEnrich, adds stricter FullEnrich filter handling (mapping + validation + exact matching), and bumps package/template versions to 0.1.301 so new apps pull in the updated tool behavior.

Changes:

  • Default PeopleSearchTool.provider to fullenrich and expand the FullEnrich filter mapping/coverage (languages, function categories, education/past roles, tenure, recent job change).
  • Add strict taxonomy validation + exact_match: true for FullEnrich string filters, plus improved email-enrichment warning surfacing.
  • Version bumps across packages and create-app templates to 0.1.301.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
packages/bubble-core/src/bubbles/tool-bubble/people-search-tool.ts FullEnrich-first behavior, strict validation/mapping, exact-match filters, enrichment warnings, doc/schema updates (with some inconsistencies noted).
packages/bubble-core/package.json Bump @bubblelab/bubble-core version to 0.1.301.
packages/bubble-runtime/package.json Bump runtime package version to 0.1.301.
packages/bubble-shared-schemas/package.json Bump shared schemas version to 0.1.301.
packages/bubble-scope-manager/package.json Bump scope manager version to 0.1.301.
packages/create-bubblelab-app/package.json Bump create-app CLI version to 0.1.301.
packages/create-bubblelab-app/templates/basic/package.json Bump template deps to ^0.1.301.
packages/create-bubblelab-app/templates/reddit-scraper/package.json Bump template deps to ^0.1.301.

/**
* Translate Crustdata-flavored function categories to FullEnrich's top-level
* function enum (see FE docs: Functions & Subfunctions). Unknown inputs are
* tracked so callers can see in `warnings` which values FE ignored. FE's
Comment on lines 598 to 603
provider: z
.enum(['crustdata', 'fullenrich'])
.default('fullenrich')
.describe(
"Search provider. Default: 'fullenrich'. Supported filter sets differ between providers — unsupported filters are silently ignored. FullEnrich does not support: locationRadius, minYearsExperience/maxYearsExperience, functionCategories, schoolName, pastJobTitle, minYearsAtCompany, recentlyChangedJobs, minConnections, excludeCompanies, excludeProfiles, languages. Crustdata supports all filters. Email enrichment (enrichEmails) only applies to Crustdata provider."
"Search provider. Default: 'fullenrich'. Leave unset unless you need a filter FullEnrich cannot honor. On the FullEnrich provider every string filter is sent with exact_match=true so results are strictly scoped (e.g. companyName='Stripe' will NOT also return 'Stripes' or ex-Stripe folks). Filters the FullEnrich provider DOES NOT SUPPORT (the tool returns a hard error if any of these are set while provider='fullenrich'): locationRadius, minYearsExperience, maxYearsExperience, minConnections, excludeCompanies, excludeProfiles, companyLinkedinUrl. Enumerated filters (seniorityLevels, functionCategories, companyIndustries) are validated against FullEnrich's taxonomy and return a hard error with the accepted values if an unknown value is passed — no silent soft-match. Everything else — including languages, schoolName, pastJobTitle, minYearsAtCompany, recentlyChangedJobs — is supported on BOTH providers."
),

**PROVIDERS:**
- \`fullenrich\` (DEFAULT, preferred): FullEnrich /people/search. Returns LinkedIn profiles,
titles, locations, employment history, and work emails when available.
Comment on lines +824 to +828
* FullEnrich's /people/search; falls back to Crustdata PersonDB only when the
* caller opts in (provider='crustdata') or relies on a filter FullEnrich
* doesn't support (locationRadius, functionCategories, schoolName, pastJobTitle,
* minYearsExperience/maxYearsExperience, recentlyChangedJobs, minConnections,
* excludeCompanies, excludeProfiles, languages).
Comment on lines +867 to +868
doesn't support — the tool will silently drop unsupported filters on FullEnrich, so
check the per-filter notes below before choosing.
Comment on lines +90 to +97
const key = raw.trim().toLowerCase();
const hit = table[key];
if (hit) {
for (const v of hit) mapped.add(v);
continue;
}
if (feEnum.has(raw)) {
mapped.add(raw);
: { mapped: [] as string[], unknown: [] as string[] };
if (seniorityResult.unknown.length > 0) {
return this.createErrorResult(
`Unknown seniorityLevels values for FullEnrich: ${seniorityResult.unknown.join(', ')}. FullEnrich accepts: Owner, Founder, C-level, Partner, VP, Head, Director, Manager, Senior. Set provider="crustdata" for the extra Crustdata-only values (Entry Level, In Training, Experienced Manager, Entry Level Manager, Strategic).`
Comment on lines +116 to +148
// Top-level FE functions — any value here passes through.
const feFunctions = new Set([
'Administrative',
'Agriculture & Environment',
'Construction & Trades',
'Consulting & Advisory',
'Customer Service',
'Design',
'Education',
'Energy & Utilities',
'Entertainment & Gaming',
'Executive & Leadership',
'Finance',
'Hospitality & Tourism',
'Human Resources',
'Legal',
'Marketing',
'Media & Communications',
'Medical & Health',
'Non-Profit & Government',
'Not Employed',
'Operations',
'Personal & Home Services',
'Product',
'Project & Program Management',
'Public Safety & Security',
'Research & Science',
'Retail & Consumer',
'Sales',
'Software',
'Traditional Engineering',
'Transportation & Logistics',
]);
Comment on lines +1375 to +1380
* filters, collecting warnings for any filter the provider cannot honor so
* callers can see why results may look narrower than expected.
*
* When `enrichEmails=true`, runs the FullEnrich bulk-enrichment pipeline as a
* post step (same path as the Crustdata branch) because /people/search does
* not return emails by itself.
Comment on lines +803 to +808
warnings: z
.array(z.string())
.optional()
.describe(
'Non-fatal signals from the tool — e.g. filters that were requested but the chosen provider does not support (and were therefore ignored). Useful for surfacing why a search returned fewer results than expected.'
),
@zhubzy zhubzy merged commit 84bf51c into main Apr 20, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants